//
//	File: MaterialRenderer.cpp
//
// Implements the renderer class.

#include "MaterialRenderer.h"
#include "AbstractMaterial.h"
#include "..\OyDirect3D9_MaterialSystem.h"


CMaterialRenderer::CMaterialRenderer() : materialStack_(INIT_MATERIALINFO_ARRAY_NUM),
	m_opaqueBatch(INIT_LARGE_RENDERBATCH_ARRAY_NUM), 
	m_opaqueAlphatestBatch(INIT_LARGE_RENDERBATCH_ARRAY_NUM),
	m_transparentBatch(INIT_SMALL_RENDERBATCH_ARRAY_NUM)
{
	m_PropertyNum = 0;
	m_FirstMaterial = 0;
	m_pd3dDevice = NULL;
	m_pSkinManager = NULL;
	m_pOyD3D9 = NULL;

	for( UINT i=0; i<INIT_LARGE_RENDERBATCH_ARRAY_NUM; i++ )
	{
		m_opaqueBatch[i] = new COpaqueRenderBatch();
		m_opaqueAlphatestBatch[i] = new COpaqueAlphaTestRenderBatch();
	}
	for( UINT j=0; j<INIT_SMALL_RENDERBATCH_ARRAY_NUM; j++ )
		m_transparentBatch[j] = new CTransparentRenderBatch();

}

CMaterialRenderer::~CMaterialRenderer()
{
	for( int i=0; i<m_opaqueBatch.Size(); i++ )
	{
		if( m_opaqueBatch[i] )
		{
			delete m_opaqueBatch[i];
			m_opaqueBatch[i] = NULL;
		}
	}

	for( int i=0; i<m_opaqueAlphatestBatch.Size(); i++ )
	{
		if( m_opaqueAlphatestBatch[i] )
		{
			delete m_opaqueAlphatestBatch[i];
			m_opaqueAlphatestBatch[i] = NULL;
		}
	}
	for( int j=0; j<m_transparentBatch.Size(); j++ )
	{
		if( m_transparentBatch[j] )
		{
			delete m_transparentBatch[j];
			m_transparentBatch[j] = NULL;
		}
	}
}

int	CMaterialRenderer::ComputeMaterialPass()
{
	// compute the first active material
	m_FirstMaterial = materialStack_.Count() - 1;
	for( int i = 0; i < m_FirstMaterial; ++i )
	{
		if( materialStack_[i].mode_ == MM_Replace )
		{
			m_FirstMaterial = i;
			break;
		}
	}

	// compute the first & last pass for each active material
	int currentPass = 0;
	for( int i = m_FirstMaterial; i >= 0; --i )
	{
		MaterialInfo & mat = materialStack_[i];

		switch( mat.mode_ )
		{
		case MM_Add :
		case MM_Replace :
			mat.firstPass_ = currentPass;
			currentPass += mat.material_->GetPassCount();
			mat.lastPass_ = currentPass - 1;
			break;

		case MM_Blend :
			mat.firstPass_ = 0;
			mat.lastPass_ = currentPass - 1;
			break;
		}
	}
	return currentPass;
}

void CMaterialRenderer::OnePassPreRenderMaterial(int pass)
{
	// pre render
	for( int j = m_FirstMaterial; j >= 0; --j )
	{
		const MaterialInfo & mat = materialStack_[j];
		if( pass >= mat.firstPass_ && pass <= mat.lastPass_ )
			mat.material_->PreRender( pass % mat.material_->GetPassCount() );
	}
}

void CMaterialRenderer::OnePassPostRenderMaterial(int pass)
{
	// post render
	for( int j = m_FirstMaterial; j >= 0; --j )
	{
		const MaterialInfo & mat = materialStack_[j];
		if( pass >= mat.firstPass_ && pass <= mat.lastPass_ )
			mat.material_->PostRender( pass % mat.material_->GetPassCount() );
	}
}

void CMaterialRenderer::Draw( CRenderBatch::BatchType BTtype )
{
	switch( materialStack_.Count() )
	{
	case 0 :
		// empty stack: nothing to draw
		break;

	case 1 :
		// one material: fast draw
		FastDraw( BTtype );
		break;

	default :
		// multiple materials: slow draw
		SlowDraw( BTtype );
		break;
	}
}



void CMaterialRenderer::FastDraw( CRenderBatch::BatchType BTtype )
{
	// render the object using a single material
	AbstractMaterial * material = materialStack_[0].material_;
	if( material )
	{
		const int numPass = material->GetPassCount();
		for( int i = 0; i < numPass; ++i )
		{
			material->PreRender( i );

			RenderBatchDrawCall(BTtype);

			material->PostRender( i );
		}
	}
}



void CMaterialRenderer::SlowDraw( CRenderBatch::BatchType BTtype )
{
	// compute the first active material
	int firstMaterial = materialStack_.Count() - 1;
	for( int i = 0; i < firstMaterial; ++i )
	{
		if( materialStack_[i].mode_ == MM_Replace )
		{
			firstMaterial = i;
			break;
		}
	}

	// compute the first & last pass for each active material
	int currentPass = 0;
	for( int i = firstMaterial; i >= 0; --i )
	{
		MaterialInfo & mat = materialStack_[i];

		switch( mat.mode_ )
		{
		case MM_Add :
		case MM_Replace :
			mat.firstPass_ = currentPass;
			currentPass += mat.material_->GetPassCount();
			mat.lastPass_ = currentPass - 1;
			break;

		case MM_Blend :
			mat.firstPass_ = 0;
			mat.lastPass_ = currentPass - 1;
			break;
		}
	}

	// render loop
	for( int i = 0; i < currentPass; ++i )
	{
		// pre render
		for( int j = firstMaterial; j >= 0; --j )
		{
			const MaterialInfo & mat = materialStack_[j];
			if( i >= mat.firstPass_ && i <= mat.lastPass_ )
				mat.material_->PreRender( i % mat.material_->GetPassCount() );
		}

		RenderBatchDrawCall(BTtype);

		for( int j = firstMaterial; j >= 0; --j )
		{
			const MaterialInfo & mat = materialStack_[j];
			if( i >= mat.firstPass_ && i <= mat.lastPass_ )
				mat.material_->PostRender( i % mat.material_->GetPassCount() );
		}
	}
}

void CMaterialRenderer::RenderBatchDrawCall( CRenderBatch::BatchType BTtype )
{
	UINT ActiveTextureID = 0xFFFFFFFF;
	UINT ActiveMaterialID = 0xFFFFFFFF;

	switch( BTtype )
	{
	case CRenderBatch::eBatchOpaque:
		{
			for( int i= 0; i<m_opaqueBatch.Count(); i++ )
			{
				m_opaqueBatch[i]->PreRender();
				m_opaqueBatch[i]->Render();
				m_opaqueBatch[i]->PostRender();
			}
		}
		break;
	case CRenderBatch::eBatchOpaqueAlphaTest:
		{
			for( int i= 0; i<m_opaqueAlphatestBatch.Count(); i++ )
			{
				m_opaqueAlphatestBatch[i]->PreRender();
				m_opaqueAlphatestBatch[i]->Render();
				m_opaqueAlphatestBatch[i]->PostRender();
			}
		}
		break;
	case CRenderBatch::eBatchTransparent:
		{
			for( int i= 0; i<m_transparentBatch.Count(); i++ )
			{
				m_transparentBatch[i]->PreRender();
				m_transparentBatch[i]->Render();
				m_transparentBatch[i]->PostRender();
			}
		}
		break;
	default:
		break;
	}
}


void CMaterialRenderer::QueueRenderBatch()
{
	return;
}

void CMaterialRenderer::BeforeDrawRenderBatch()
{
	m_opaqueBatch.Clear();
	m_opaqueAlphatestBatch.Clear();
	m_transparentBatch.Clear();
}

void CMaterialRenderer::AfterDrawRenderBatch()
{
	m_opaqueBatch.Clear();
	m_opaqueAlphatestBatch.Clear();
	m_transparentBatch.Clear();
}

void CMaterialRenderer::DoDrawRenderBatch_Opaque()
{
	OyMaterialSystem::MaterialList &Mat_List = GetOyMaterialSystem()->GetMaterialList();

	// Normal opaqe / alpha-test RenderBatch
	if( m_opaqueBatch.Count() > 0 )
	{
		const OyMaterialSystem::D3D9MaterialInfo &OpaqueMaterial_info = Mat_List[OyMaterialSystem::eMaterial_opaque_base];
		PushMaterial( OpaqueMaterial_info.m_material, MM_Add );
		Draw( CRenderBatch::eBatchOpaque );
		PopMaterial();
	}

	if( m_opaqueAlphatestBatch.Count() > 0 )
	{
		const OyMaterialSystem::D3D9MaterialInfo &AlphaTestMaterial_info = Mat_List[OyMaterialSystem::eMaterial_opaque_alphatest];
		PushMaterial( AlphaTestMaterial_info.m_material, MM_Add );
		Draw( CRenderBatch::eBatchOpaqueAlphaTest );
		PopMaterial();
	}
}

void CMaterialRenderer::DoDrawRenderBatch_Transparent()
{
	OyMaterialSystem::MaterialList &Mat_List = GetOyMaterialSystem()->GetMaterialList();

	// Normal transparent RenderBatch
	if( m_transparentBatch.Count() > 0 )
	{
		const OyMaterialSystem::D3D9MaterialInfo &TransparentMaterial_info = Mat_List[OyMaterialSystem::eMaterial_transparent];
		PushMaterial( TransparentMaterial_info.m_material, MM_Add );
		Draw( CRenderBatch::eBatchTransparent );
		PopMaterial();
	}
}

void CMaterialRenderer::PushOpaqueRenderBatch(const COpaqueRenderBatch& batch)
{
	if( m_opaqueBatch.Size() == m_opaqueBatch.Count() )
	{
		// Full
		m_opaqueBatch.Resize( m_opaqueBatch.Size() + INIT_LARGE_RENDERBATCH_ARRAY_NUM );
		for( int i=m_opaqueBatch.Size(); i<m_opaqueBatch.Size()+INIT_LARGE_RENDERBATCH_ARRAY_NUM; i++ )
			m_opaqueBatch[i] = new COpaqueRenderBatch();
	}
	COpaqueRenderBatch *pNewBatch = m_opaqueBatch[m_opaqueBatch.Count()];
	pNewBatch->m_BatchType = batch.m_BatchType;
	pNewBatch->m_BBOXCenter = batch.m_BBOXCenter;
	pNewBatch->m_MatWorld = batch.m_MatWorld;
	pNewBatch->m_pSB = batch.m_pSB;
	pNewBatch->m_pVC = batch.m_pVC;

	m_opaqueBatch.Push(pNewBatch);
}

void CMaterialRenderer::PushAlphaTestRenderBatch(const COpaqueAlphaTestRenderBatch& batch)
{
	if( m_opaqueAlphatestBatch.Size() == m_opaqueAlphatestBatch.Count() )
	{
		// Full
		m_opaqueAlphatestBatch.Resize( m_opaqueAlphatestBatch.Size() + INIT_LARGE_RENDERBATCH_ARRAY_NUM );
		for( int i=m_opaqueAlphatestBatch.Size(); i<m_opaqueAlphatestBatch.Size()+INIT_LARGE_RENDERBATCH_ARRAY_NUM; i++ )
			m_opaqueAlphatestBatch[i] = new COpaqueAlphaTestRenderBatch();
	}
	COpaqueAlphaTestRenderBatch *pNewBatch = m_opaqueAlphatestBatch[m_opaqueAlphatestBatch.Count()];
	pNewBatch->m_BatchType = batch.m_BatchType;
	pNewBatch->m_BBOXCenter = batch.m_BBOXCenter;
	pNewBatch->m_MatWorld = batch.m_MatWorld;
	pNewBatch->m_pSB = batch.m_pSB;
	pNewBatch->m_pVC = batch.m_pVC;

	m_opaqueAlphatestBatch.Push(pNewBatch);
}

void CMaterialRenderer::PushTransparentRenderBatch(const CTransparentRenderBatch& batch)
{
	if( m_transparentBatch.Size() == m_transparentBatch.Count() )
	{
		// Full
		m_transparentBatch.Resize( m_transparentBatch.Size() + INIT_SMALL_RENDERBATCH_ARRAY_NUM );
		for( int i=m_transparentBatch.Size(); i<m_transparentBatch.Size()+INIT_SMALL_RENDERBATCH_ARRAY_NUM; i++ )
			m_transparentBatch[i] = new CTransparentRenderBatch();
	}

	CTransparentRenderBatch *pNewBatch = m_transparentBatch[m_transparentBatch.Count()];
	pNewBatch->m_BatchType = batch.m_BatchType;
	pNewBatch->m_BBOXCenter = batch.m_BBOXCenter;
	pNewBatch->m_MatWorld = batch.m_MatWorld;
	pNewBatch->m_pSB = batch.m_pSB;
	pNewBatch->m_pVC = batch.m_pVC;

	m_transparentBatch.Push(pNewBatch);
}